package za.org.jcicada.confss.impl;

import java.net.URI;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import java.util.Timer;
import java.util.TimerTask;

import org.apache.commons.lang3.Pair;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import za.org.jcicada.cfgmngr.IConfigurationClient;
import za.org.jcicada.cfgmngr.IConfigurationServer;
import za.org.jcicada.genss.ServiceURN;
import za.org.jcicada.genss.SystemURN;


public class ConfssProcessing {
    private static final Log logger = LogFactory.getLog(ConfssProcessing.class);
    
    /**
     * *********************************************************************************************
     * Processing lists of services registered/toRegister/toDeregister/ actions to perform
     * *********************************************************************************************
     */
    
    protected Set<SystemURN> systemsToConfigure = Collections.synchronizedSet(new HashSet<SystemURN>());
    
    protected Set<SystemURN> systemsToRegister = Collections.synchronizedSet(new HashSet<SystemURN>());
    protected Set<SystemURN> systemsToDeregister = Collections.synchronizedSet(new HashSet<SystemURN>());
    
    protected Set<Pair<ServiceURN, URI>> servicesToRegister = Collections.synchronizedSet(new HashSet<Pair<ServiceURN, URI>>());
    protected Set<Pair<ServiceURN, URI>> servicesToDeregister = Collections.synchronizedSet(new HashSet<Pair<ServiceURN, URI>>());

    protected boolean serviceProcessingStarted = false;
    
    /** Handle to configuration server for sending requests */
    private IConfigurationServer configurationServer;
    
    /** Handle to configuration client for receiving replies */
    private final IConfigurationClient configurationClient;
    
    /** Timer task used for scheduling retries */
    private TimerTask timerTask;
    
    /**
     * Constructor
     * 
     * @param configurationClient handle to the client for receiving replies
     * @param period_ms retry period in milliseconds
     */
    public ConfssProcessing(final IConfigurationClient configurationClient) {
    	if (configurationClient == null) {
            throw new IllegalArgumentException();
        } else {
            this.configurationClient = configurationClient;
        }
    }
    
    /**
     * Initialize the processor by setting up a timer to retry registration 
     * requests at a specified period.
     * 
     * @param configurationServer handle to the configuration server for sending requests
     * @param timer handle to timer object used for scheduling retries
     * @param period_ms retry period in milliseconds
     */
    public void initialize(final IConfigurationServer configurationServer, Timer timer, long period_ms) {
    	if (timerTask != null) {
    		throw new IllegalStateException();
    	}
    	if (configurationServer == null) {
    		throw new IllegalArgumentException();
    	}
    	this.configurationServer = configurationServer;
    	
    	timerTask = new TimerTask() {

			@Override
			public void run() {
				logger.trace("ConfssProcessing Timer Task");
				if (configurationServer != null) {
					synchronized(systemsToConfigure) {
						for (SystemURN systemURN : systemsToConfigure) {
							configurationServer.requestGetConfiguration(configurationClient, systemURN);
						}
					}
					synchronized(systemsToRegister) {
						for (SystemURN systemURN : systemsToRegister) {
							configurationServer.requestRegisterSystem(configurationClient, systemURN);
						}
					}
					synchronized(systemsToDeregister) {
						for (SystemURN systemURN : systemsToDeregister) {
							configurationServer.requestDeregisterSystem(configurationClient, systemURN);
						}
					}
					registerServices();
				}
			}
        	
        };
        timer.scheduleAtFixedRate(timerTask, 0L, period_ms);
    }
    
    /** 
     * Stop all the timers to prepare for shutdown
     */
    public void shutdownProcessing() {
        logger.debug("shutdownProcessing");
        
        synchronized(systemsToConfigure) {
        	systemsToConfigure.clear();
        }
        synchronized(systemsToRegister) {
        	systemsToRegister.clear();
        }
        synchronized(systemsToDeregister) {
        	systemsToDeregister.clear();
        }
        synchronized(servicesToRegister) {
        	servicesToRegister.clear();
        }
        synchronized(servicesToDeregister) {
        	servicesToDeregister.clear();
        }
        if (timerTask != null) {
        	timerTask.cancel();
        	timerTask = null;
        }
    }
    
    public Set<SystemURN> getSystemsToConfigure() {
    	return systemsToConfigure;
    }
    
    public Set<SystemURN> getSystemsToRegister() {
    	return systemsToRegister;
    }
    
    public Set<SystemURN> getSystemsToDeregister() {
    	return systemsToDeregister;
    }
    
    public Set<Pair<ServiceURN, URI>> getServicesToRegister() {
    	return servicesToRegister;
    }
    
    public Set<Pair<ServiceURN, URI>> getServicesToDeregister() {
    	return servicesToDeregister;
    }
    
    /* *************************************************
     * Handle periodic configuration requests to CfgMngr
     * *************************************************/
    public void addConfigurationRequest(SystemURN systemURN) {
    	configurationServer.requestGetConfiguration(configurationClient, systemURN);
    	synchronized(systemsToConfigure) {
    		systemsToConfigure.add(systemURN);
    	}
    }
    
    public void removeConfigurationRequest(SystemURN systemURN) {
    	synchronized(systemsToConfigure) {
    		systemsToConfigure.remove(systemURN);
    	}
    }
    
    /* *************************************************
     * Handle periodic system registration to CfgMngr
     * *************************************************/
    public void addSystemToRegister(SystemURN systemURN) {
    	configurationServer.requestRegisterSystem(configurationClient, systemURN);
    	synchronized(systemsToDeregister) {
    		systemsToDeregister.remove(systemURN);
    	}
    	synchronized(systemsToRegister) {
    		systemsToRegister.add(systemURN);
    	}
    }

    public void removeSystemToRegister(SystemURN systemURN) {
    	synchronized(systemsToRegister) {
    		systemsToRegister.remove(systemURN);
    	}
    }
    
    /* *************************************************
     * Handle system deregistration to CfgMngr
     * *************************************************/
    public void addSystemToDeregister(SystemURN systemURN) {
    	if (configurationServer != null) {
    		configurationServer.requestDeregisterSystem(configurationClient, systemURN);
    	}
    	synchronized(systemsToRegister) {
    		systemsToRegister.remove(systemURN);
    	}
    	synchronized(systemsToDeregister) {
    		systemsToDeregister.add(systemURN);
    	}
    }
    
    public void removeSystemToDeregister(SystemURN systemURN) {
    	synchronized(systemsToDeregister) {
    		systemsToDeregister.remove(systemURN);
    	}
    }
    
    /* *************************************************
     * Handle service registration to CfgMngr
     * *************************************************/
    public void addServiceToRegister(Pair<ServiceURN, URI> pair) {
    	if (serviceProcessingStarted && configurationServer != null) {
    		configurationServer.requestRegisterService (configurationClient, pair.right, pair.left);
    	}
    	synchronized(servicesToDeregister) {
    		servicesToDeregister.remove(pair);
    	}
    	synchronized(servicesToRegister) {
    		servicesToRegister.add(pair);
    	}
    }
    
    public void removeServiceToRegister(Pair<ServiceURN, URI> pair) {
    	synchronized(servicesToRegister) {
    		servicesToRegister.remove(pair);
    	}
    }

    public synchronized void allowServiceProcessing(boolean allow) {
        logger.debug("allowServiceProcessing");
        serviceProcessingStarted = allow;
        registerServices();
    }
    
    public boolean isServiceProcessingAllowed () {
        return (serviceProcessingStarted);
    }  
    
    /* *************************************************
     * Handle service deregistration to CfgMngr
     * *************************************************/
    public void addServiceToDeregister(Pair<ServiceURN, URI> pair) {
    	if (serviceProcessingStarted && configurationServer != null) {
    		configurationServer.requestDeregisterService (configurationClient, pair.right, pair.left);
    	}
    	synchronized(servicesToRegister) {
    		servicesToRegister.remove(pair);
    	}
    	synchronized(servicesToDeregister) {
    		servicesToDeregister.add(pair);
    	}
    }
    
    public void removeServiceToDeregister(Pair<ServiceURN, URI> pair) {
    	synchronized(servicesToDeregister) {
    		servicesToDeregister.remove(pair);
    	}
    }

    private void registerServices() {
		if (serviceProcessingStarted) {
			synchronized(servicesToRegister) {
				for (Pair<ServiceURN, URI> entry : new HashSet<Pair<ServiceURN, URI>>(servicesToRegister)) {
					configurationServer.requestRegisterService(configurationClient, entry.right, entry.left);
				}
			}
			synchronized(servicesToDeregister) {
				for (Pair<ServiceURN, URI> entry : new HashSet<Pair<ServiceURN, URI>>(servicesToDeregister)) {
					configurationServer.requestDeregisterService(configurationClient, entry.right, entry.left);
				}
			}
		}
    }

}
